'''
统计未出现的类别
'''
import pandas as pd
import numpy as np
df = pd.DataFrame({'A':['a','b','c','a'],
                   'B':['cat','cat','dog','cat']})
# 对两列的数据进行统计
s = pd.crosstab(df.A, df.B)
print(s)

df.B = df.B.astype('category').cat.add_categories('sheep')
s = pd.crosstab(df.A, df.B, dropna=False)
print(s)

'''
实现一个带有 dropna 参数的 my_crosstab 函数来完成上面的功能。
'''

def my_crosstab(s1,s2,dropna=True):
    idx1 = (s1.cat.categories if s1.dtype.name == 'category' and
                                 not dropna else s1.unique())
    idx2 = (s2.cat.categories if s2.dtype.name == 'category' and
                                 not dropna else s2.unique())
    res = pd.DataFrame(np.zeros((idx1.shape[0], idx2.shape[0])),
                       index=idx1, columns=idx2)
    for i,j in zip(s1,s2):
        res.at[i,j] +=1
    res = res.rename_axis(index=s1.name, columns=s2.name).astype('int')
    return res
#df.B = df.B.astype('category').cat.add_categories('sheep')
r=my_crosstab(df.A,df.B,True)
print(r)
r2 = my_crosstab(df.A,df.B,False)
print(r2)

'''
ext2
分别对 df.cut 在 object 类型和 category 类型下使用 nunique 函数，并比较它们的性能。
'''
df = pd.read_csv('data/diamonds.csv')
s_obj, s_cat = df.cut, df.cut.astype('category')
#%timeit -n 30 s_obj.nunique()
#%timeit -n 30 s_cat.nunique()

'''
钻石的切割质量可以分为五个等级，由次到好分别是 Fair, Good, Very Good, Premium, Ideal ，纯净度有八个等级，由次到好分别是 I1, SI2, SI1, VS2, VS1, VVS2, VVS1, IF ，请对切割质量按照 由好到次 的顺序排序，相同切割质量的钻石，按照纯净度进行 由次到好 的排序
'''
df.cut = df.cut.astype('category').cat.reorder_categories([
        'Fair', 'Good', 'Very Good', 'Premium', 'Ideal'],ordered=True)
df.clarity = df.clarity.astype('category').cat.reorder_categories([
        'I1', 'SI2', 'SI1', 'VS2', 'VS1', 'VVS2', 'VVS1', 'IF'],ordered=True)
res = df.sort_values(['cut', 'clarity'], ascending=[False, True])
'''
分别采用两种不同的方法，把 cut, clarity 这两列按照 由好到次 的顺序，映射到从0到n-1的整数，其中n表示类别的个数
'''
df.cut = df.cut.cat.reorder_categories(
        df.cut.cat.categories[::-1])
df.clarity = df.clarity.cat.reorder_categories(
            df.clarity.cat.categories[::-1])
df.cut = df.cut.cat.codes # 方法一：利用cat.codes
clarity_cat = df.clarity.cat.categories
df.clarity = df.clarity.replace(dict(zip(
            clarity_cat, np.arange(
                len(clarity_cat))))) # 方法二：使用replace映射

'''
4.对每克拉的价格分别按照分位数（q=[0.2, 0.4, 0.6, 0.8]）与[1000, 3500, 5500, 18000]割点进行分箱得到五个类别 Very Low, Low, Mid, High, Very High ，并把按这两种分箱方法得到的 category 序列依次添加到原表中
'''
q = [0, 0.2, 0.4, 0.6, 0.8, 1]
point = [-np.infty, 1000, 3500, 5500, 18000, np.infty]
avg = df.price / df.carat
df['avg_cut'] = pd.cut(avg, bins=point, labels=[
                'Very Low', 'Low', 'Mid', 'High', 'Very High'])
df['avg_qcut'] = pd.qcut(avg, q=q, labels=[
                'Very Low', 'Low', 'Mid', 'High', 'Very High'])
print(df.head())

'''
第4问中按照整数分箱得到的序列中，是否出现了所有的类别？如果存在没有出现的类别请把该类别删除
'''
df.avg_cut.unique()
df.avg_cut.cat.categories
df.avg_cut = df.avg_cut.cat.remove_categories([
            'Very Low', 'Very High'])
df.avg_cut.head(3)

'''
6.对第4问中按照分位数分箱得到的序列，求每个样本对应所在区间的左右端点值和长度。
'''
interval_avg = pd.IntervalIndex(pd.qcut(avg, q=q))
interval_avg.right.to_series().reset_index(drop=True).head(3)
interval_avg.left.to_series().reset_index(drop=True).head(3)
interval_avg.length.to_series().reset_index(drop=True).head(3)